package find

import (
	"archive/zip"
	"compress/gzip"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type IpInfo struct {
	prefStart [256]uint32
	prefEnd   [256]uint32
	data      interface{}
	mode      string
	fileName  string
}

type IpInfoV6 struct {
	prefStart map[uint32]uint32
	prefEnd   map[uint32]uint32
	numbers   uint32
	data      interface{}
	mode      string
	fileName  string
}

var objV6 *IpInfoV6
var onceV6 sync.Once
var objLocation *IpInfo
var onceLocation sync.Once
var objScenes *IpInfo
var onceScenes sync.Once
var objProxy *IpInfo
var onceProxy sync.Once

func getOnceV6() *sync.Once {
	return &onceV6
}
func getOnceV4Location() *sync.Once {
	return &onceLocation
}
func getOnceV4Scenes() *sync.Once {
	return &onceScenes
}
func getOnceV4Proxy() *sync.Once {
	return &onceProxy
}

func GetObjectV4LocationFile(fileName string) (*IpInfo, error) {
	newOnce := getOnceV4Location()
	var err error
	newOnce.Do(func() {
		objLocation = &IpInfo{}
		objLocation, err = loadFile(fileName)
		if err != nil {
			log.Print("Ip database load failed")
			return
		}
		objLocation.fileName = fileName
		objLocation.mode = "file"

	})
	if err != nil {
		return nil, err
	}
	return objLocation, err
}

func GetObjectV4LocationMemory(fileName string) (*IpInfo, error) {
	newOnce := getOnceV4Location()
	var err error
	newOnce.Do(func() {
		objLocation = &IpInfo{}
		objLocation, err = loadFileMem(fileName)
		if err != nil {
			log.Print("Ip database load failed")
			return
		}
		objLocation.fileName = fileName
		objLocation.mode = "mem"

	})
	if err != nil {
		return nil, err
	}
	return objLocation, err
}

func GetObjectV4ScenesFile(fileName string) (*IpInfo, error) {
	newOnce := getOnceV4Scenes()
	var err error
	newOnce.Do(func() {
		objScenes = &IpInfo{}
		objScenes, err = loadFile(fileName)
		if err != nil {
			log.Print("Ip database load failed")
			return
		}
		objScenes.fileName = fileName
		objScenes.mode = "file"

	})
	if err != nil {
		return nil, err
	}
	return objScenes, err
}
func GetObjectV4ScenesMemory(fileName string) (*IpInfo, error) {
	newOnce := getOnceV4Scenes()
	var err error
	newOnce.Do(func() {
		objScenes = &IpInfo{}
		objScenes, err = loadFileMem(fileName)
		if err != nil {
			log.Print("Ip database load failed")
			return
		}
		objScenes.fileName = fileName
		objScenes.mode = "mem"
	})

	if err != nil {
		return nil, err
	}
	return objScenes, err
}

func GetObjectV4ProxyFile(fileName string) (*IpInfo, error) {
	newOnce := getOnceV4Proxy()
	var err error
	newOnce.Do(func() {
		objProxy = &IpInfo{}
		objProxy, err = loadFile(fileName)
		if err != nil {
			log.Println("Ip database load failed")
			return
		}
		objProxy.fileName = fileName
		objProxy.mode = "file"
	})
	if err != nil {
		return nil, err
	}
	return objProxy, err
}
func GetObjectV4ProxyMemory(fileName string) (*IpInfo, error) {
	newOnce := getOnceV4Proxy()
	var err error
	newOnce.Do(func() {
		objProxy = &IpInfo{}
		objProxy, err = loadFileMem(fileName)
		if err != nil {
			log.Println("Ip database load failed")
			return
		}
		objProxy.fileName = fileName
		objProxy.mode = "mem"

	})
	if err != nil {
		return nil, err
	}
	return objProxy, err
}

func GetObjectV6File(filePath string, mode string) (*IpInfoV6, error) {
	newOnceV6 := getOnceV6()
	var err error
	newOnceV6.Do(func() {
		objV6 = &IpInfoV6{}
		objV6, err = loadFileV6(filePath)
		objV6.fileName = filePath
		objV6.mode = "file"
		if err != nil {
			log.Println("load database failed")
			return
		}
	})
	if err != nil {
		return nil, err
	}
	return objV6, nil
}

func GetObjectV6Memory(filePath string) (*IpInfoV6, error) {
	newOnceV6 := getOnceV6()
	var err error
	newOnceV6.Do(func() {
		objV6 = &IpInfoV6{}
		objV6, err = loadFileMemV6(filePath)
		objV6.fileName = filePath
		objV6.mode = "mem"
		if err != nil {
			log.Println("load database failed")
			return
		}
	})
	if err != nil {
		return nil, err
	}
	return objV6, nil
}

func loadFile(fileName string) (*IpInfo, error) {
	p := IpInfo{}
	file, err := os.Open(fileName)
	if err != nil {
		fmt.Println("open file failed:", err)
		return nil, err
	}
	//defer file.Close()

	p.data = file

	dataStr, err := getDataString(p.data.(*os.File), 2052, 0)
	if err != nil {
		return nil, err
	}

	for k := 0; k < 256; k++ {
		i := k*8 + 4
		p.prefStart[k] = unpackInt4byte(dataStr[i], dataStr[i+1], dataStr[i+2], dataStr[i+3])
		p.prefEnd[k] = unpackInt4byte(dataStr[i+4], dataStr[i+5], dataStr[i+6], dataStr[i+7])
	}
	return &p, err
}

func loadFileMem(file string) (*IpInfo, error) {
	p := IpInfo{}
	data, err := ioutil.ReadFile(file)
	p.data = data
	if err != nil {
		return nil, err
	}

	for k := 0; k < 256; k++ {
		i := k*8 + 4
		p.prefStart[k] = unpackInt4byte(data[i], data[i+1], data[i+2], data[i+3])
		p.prefEnd[k] = unpackInt4byte(data[i+4], data[i+5], data[i+6], data[i+7])
	}
	return &p, err
}

func loadFileV6(fileName string) (*IpInfoV6, error) {
	p := IpInfoV6{}
	var err error
	file, err := os.Open(fileName)
	if err != nil {
		fmt.Println("open file failed:", err)
		return nil, err
	}
	p.data = file

	dataStr1, err := getDataString(p.data.(*os.File), 8, 0)
	if err != nil {
		return nil, err
	}
	p.numbers = unpackInt4byte(dataStr1[4], dataStr1[5], dataStr1[6], dataStr1[7])
	p.prefStart = make(map[uint32]uint32)
	p.prefEnd = make(map[uint32]uint32)

	dataStr2, err := getDataString(p.data.(*os.File), int(p.numbers*12+4+4), 0)
	if err != nil {
		return nil, err
	}
	for k := uint32(0); k < p.numbers; k++ {
		i := k*12 + 4 + 4
		key := unpackInt4byte(dataStr2[i+8], dataStr2[i+9], dataStr2[i+10], dataStr2[i+11])
		p.prefStart[key] = unpackInt4byte(dataStr2[i], dataStr2[i+1], dataStr2[i+2], dataStr2[i+3])
		p.prefEnd[key] = unpackInt4byte(dataStr2[i+4], dataStr2[i+5], dataStr2[i+6], dataStr2[i+7])
	}

	return &p, err
}

func loadFileMemV6(file string) (*IpInfoV6, error) {
	p := IpInfoV6{}
	var err error
	data, err := ioutil.ReadFile(file)
	p.data = data
	if err != nil {
		return nil, err
	}

	p.numbers = unpackInt4byte(data[4], data[5], data[6], data[7])
	p.prefStart = make(map[uint32]uint32)
	p.prefEnd = make(map[uint32]uint32)
	for k := uint32(0); k < p.numbers; k++ {
		i := k*12 + 4 + 4
		key := unpackInt4byte(data[i+8], data[i+9], data[i+10], data[i+11])
		p.prefStart[key] = unpackInt4byte(data[i], data[i+1], data[i+2], data[i+3])
		p.prefEnd[key] = unpackInt4byte(data[i+4], data[i+5], data[i+6], data[i+7])
	}

	return &p, err

}

func (p *IpInfo) GetByNet(ip net.IP) (string, error) {
	var ip4 net.IP
	if ip4 = ip.To4(); ip4 == nil {
		return "", errors.New("IP version error")
	}

	prefix := ip4[0]
	intIP := binary.BigEndian.Uint32(ip.To4())

	low := p.prefStart[prefix]
	high := p.prefEnd[prefix]

	var cur uint32
	if low == high {
		cur = low
	} else {
		cur = p.search(low, high, intIP)
	}
	if cur == 100000000 {
		return "no information", errors.New("no information")
	} else {
		return p.getAddr(cur), nil
	}

}

func (p *IpInfo) Get(ip string) (string, error) {
	var ipNet net.IP
	var ip4 net.IP
	if ipNet = net.ParseIP(ip); ipNet == nil {
		return "", errors.New("IP version error")
	}
	if ip4 = ipNet.To4(); ip4 == nil {
		return "", errors.New("IP version error")
	}

	prefix := ip4[0]
	intIP := binary.BigEndian.Uint32(ipNet.To4())

	low := p.prefStart[prefix]
	high := p.prefEnd[prefix]

	var cur uint32
	if low == high {
		cur = low
	} else {
		cur = p.search(low, high, intIP)
	}
	if cur == 100000000 {
		return "no information", errors.New("no information")
	} else {
		return p.getAddr(cur), nil
	}

}

func (p *IpInfo) search(low uint32, high uint32, k uint32) uint32 {
	var M uint32 = 0
	for low <= high {
		mid := (low + high) / 2
		j := 2052 + (mid * 9)
		var endipNum uint32
		if p.mode == "file" {
			dataStr, err := getDataString(p.data.(*os.File), 4, int64(j))
			if err != nil {
				return 0
			}
			endipNum = unpackInt4byte(dataStr[0], dataStr[1], dataStr[2], dataStr[3])
		} else {
			dataStr := p.data.([]byte)
			endipNum = unpackInt4byte(dataStr[j], dataStr[1+j], dataStr[2+j], dataStr[3+j])
		}

		if endipNum >= k {
			M = mid
			if mid == 0 {
				break
			}
			high = mid - 1
		} else {
			low = mid + 1
		}
	}
	return M

}

func (p *IpInfo) getAddr(cur uint32) string {
	j := 2052 + (cur * 9)
	if p.mode == "file" {
		dataStr1, err := getDataString(p.data.(*os.File), 9, int64(j))
		if err != nil {
			return ""
		}
		offset := unpackInt4byte(dataStr1[4], dataStr1[5], dataStr1[6], dataStr1[7])
		length := uint32(dataStr1[8])
		dataStr2, err := getDataString(p.data.(*os.File), int(length), int64(offset))
		if err != nil {
			return ""
		}
		return string(dataStr2[0:int(length)])
	} else {
		dataStr := p.data.([]byte)
		offset := unpackInt4byte(dataStr[4+j], dataStr[5+j], dataStr[6+j], dataStr[7+j])
		length := uint32(dataStr[8+j])
		return string(dataStr[offset:int(offset+length)])
	}
}

func (p *IpInfoV6) GetByNet(ip net.IP) (string, error) {
	var ip4 net.IP
	var ip16 net.IP

	if ip4 = ip.To4(); ip4 != nil {
		return "", errors.New("IP version error")
	}
	if ip16 = ip.To16(); ip16 == nil {
		return "", errors.New("IP version error")
	}
	prefix := uint32(ip[0])<<8 | uint32(ip[1])

	intIP, err := ipToIntV6(ip16)
	if err != nil {
		return "", err
	}

	low := p.prefStart[prefix]
	high := p.prefEnd[prefix]

	var cur uint32
	if low == high {
		cur = low
	} else {
		cur = p.search(low, high, intIP)
	}
	return p.getAddr(cur), nil

}

func (p *IpInfoV6) Get(ip string) (string, error) {
	var ipNet net.IP
	var ip4 net.IP
	var ip16 net.IP

	// 对ip进行校验
	if ipNet = net.ParseIP(ip); ipNet == nil {
		return "", errors.New("IP version error")
	}
	if ip4 = ipNet.To4(); ip4 != nil {
		return "", errors.New("IP version error")
	}
	if ip16 = ipNet.To16(); ip16 == nil {
		return "", errors.New("IP version error")
	}
	prefix := uint32(ipNet[0])<<8 | uint32(ipNet[1])

	intIP, err := ipToIntV6(ip16)
	if err != nil {
		return "", err
	}

	low := p.prefStart[prefix]
	high := p.prefEnd[prefix]

	var cur uint32
	if low == high {
		cur = low
	} else {
		cur = p.search(low, high, intIP)
	}
	return p.getAddr(cur), nil

}

func (p *IpInfoV6) getAddr(cur uint32) string {
	j := p.numbers*12 + 4 + 4 + (cur * 55)
	if p.mode == "file" {
		dataStr1, err := getDataString(p.data.(*os.File), 55, int64(j))
		if err != nil {
			return ""
		}
		offset := unpackInt4byte(dataStr1[50], dataStr1[1+50], dataStr1[2+50], dataStr1[3+50])
		length := uint32(dataStr1[50+4])
		dataStr2, err := getDataString(p.data.(*os.File), int(length), int64(offset))
		if err != nil {
			return ""
		}
		return string(dataStr2[0:int(length)])
	} else {
		dataStr := p.data.([]byte)
		offset := unpackInt4byte(dataStr[j+50], dataStr[1+j+50], dataStr[2+j+50], dataStr[3+j+50])
		length := uint32(dataStr[50+j+4])
		return string(dataStr[offset:int(offset+length)])
	}
}

func (p *IpInfoV6) search(low uint32, high uint32, k *big.Int) uint32 {
	var M uint32 = 0
	for low <= high {
		mid := (low + high) / 2
		j := p.numbers*12 + 4 + 4 + (mid * 55)
		endipnum := new(big.Int)
		endipnumInt := new(big.Int)

		if p.mode == "file" {
			dataStr, err := getDataString(p.data.(*os.File), 50, int64(j))
			if err != nil {
				return 0
			}
			endipnumInt, _ = endipnum.SetString(strings.ReplaceAll(string(dataStr[0:50]), "*", ""), 10)
		} else {
			dataStr := p.data.([]byte)
			endipnumInt, _ = endipnum.SetString(strings.ReplaceAll(string(dataStr[j:j+50]), "*", ""), 10)
		}

		if endipnumInt.Cmp(k) == 0 || endipnumInt.Cmp(k) == 1 {
			M = mid
			if mid == 0 {
				break
			}
			high = mid - 1
		} else {
			low = mid + 1
		}
	}

	return M
}

func ipToIntV6(ip net.IP) (*big.Int, error) {
	IPv6Int := big.NewInt(0)
	IPv6Int.SetBytes(ip)
	return IPv6Int, nil
}

func ipToInt(ipStr string) (uint32, error) {
	ip := net.ParseIP(ipStr)
	ip = ip.To4()
	if ip == nil {
		return 0, errors.New("illegal IPv4 address")
	}
	return binary.BigEndian.Uint32(ip), nil
}

func getDataString(file *os.File, readNum int, readStart int64) (result []byte, err error) {
	buf := make([]byte, readNum)
	data, err := file.ReadAt(buf, readStart)
	if err != nil {
		return make([]byte, 0), err
	}
	dataStr := buf[:data]
	return dataStr, nil
}

func unpackInt4byte(a, b, c, d byte) uint32 {
	return (uint32(a) & 0xFF) | ((uint32(b) << 8) & 0xFF00) | ((uint32(c) << 16) & 0xFF0000) | ((uint32(d) << 24) & 0xFF000000)
}

func Update(downloadUrl string, outputPath string) error {
	client := http.Client{}
	resp, err := client.Get(downloadUrl)
	if err != nil {
		return fmt.Errorf("request error: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("request failed,status_code: %s", resp.Status)
	}
	// 从request中获取下载文件的信息
	filePaths := strings.Split(resp.Request.URL.Path, "/")
	filePath := filePaths[len(filePaths)-1]
	// 拼接存放路径
	realFilePath := outputPath + filePath
	// 创建文件
	out, err := os.Create(realFilePath)
	if err != nil {
		return fmt.Errorf("create file failed: %v", err)
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return fmt.Errorf("failed to receive content from response: %v", err)
	}
	//解压
	if strings.Contains(realFilePath, ".zip") {
		err = unzipFile(realFilePath, outputPath)
	} else if strings.Contains(realFilePath, ".gz") {
		err = ungzipFile(realFilePath, outputPath)
	}
	if err != nil {
		return fmt.Errorf("unzip failed: %v", err)
	}
	fmt.Println("download complete")
	return nil
}

// 解压zip
func unzipFile(zipPath string, outputDir string) error {
	// 打开压缩文件
	r, err := zip.OpenReader(zipPath)
	if err != nil {
		return fmt.Errorf("error opening file in zip: %v", err)
	}
	defer r.Close()

	// 确保目标输出目录存在
	err = os.MkdirAll(outputDir, 0755)
	if err != nil {
		return fmt.Errorf("error creating output directory: %v", err)
	}

	// 遍历压缩文件中的文件
	for _, f := range r.File {
		// 打开文件
		rc, err := f.Open()
		if err != nil {
			return fmt.Errorf("error copying file contents: %v", err)
		}
		defer rc.Close()

		// 创建文件路径
		outPath := filepath.Join(outputDir, f.Name)

		// 创建目标文件
		w, err := os.Create(outPath)
		if err != nil {
			return fmt.Errorf("error creating file: %v", err)
		}
		defer w.Close()

		// 写入文件内容
		_, err = io.Copy(w, rc)
		if err != nil {
			return fmt.Errorf("error copying file contents: %v", err)
		}

		// 文件已找到并写入成功，退出循环
		break
	}
	err = os.Remove(zipPath)
	if err != nil {
		return fmt.Errorf("error deleting zip file: %v", err)
	}
	return nil
}

func ungzipFile(gzFilePath string, outputDir string) error {
	f, err := os.Open(gzFilePath)
	if err != nil {
		return fmt.Errorf("error opening gzip file: %v", err)
	}
	defer f.Close()

	err = os.MkdirAll(outputDir, 0755)
	if err != nil {
		return fmt.Errorf("error creating output directory: %v", err)
	}

	fileName := filepath.Base(gzFilePath)
	origFileName := strings.TrimSuffix(fileName, filepath.Ext(fileName))
	outPath := filepath.Join(outputDir, origFileName)
	w, err := os.Create(outPath)
	if err != nil {
		return fmt.Errorf("error creating file: %v", err)
	}
	defer w.Close()

	r, err := gzip.NewReader(f)
	if err != nil {
		return fmt.Errorf("error creating gzip reader: %v", err)
	}
	defer r.Close()

	_, err = io.Copy(w, r)
	if err != nil {
		return fmt.Errorf("error copying file contents: %v", err)
	}

	err = os.Remove(gzFilePath)
	if err != nil {
		return fmt.Errorf("error deleting gzip file: %v", err)
	}

	return nil
}
